package org.wall.controller

import grails.transaction.*
import grails.rest.*
import org.wall.domain.*
import grails.converters.*
import grails.plugin.springsecurity.annotation.Secured

class MapaController extends RestfulController<Mapa> {

    static responseFormats = ["json"]

    MapaController() {
        super(Mapa)
    }

    @Secured(['permitAll'])
    def index() {
        render "Mapa Controller..."
    }

    /*
    * Retorna o json da requisição
    */
    def getJSONRequest() {

        def json

        try {
            json  = request.JSON;   
        }
        catch(Exception e) {
            println json
            println e
            render ([erro: "O formato JSON é inválido!!!"] as JSON)
        }

        json
    }

    //TODO Tratar fluxos de excecao ao gravar um mapa
    @Override
    @Secured(['ROLE_ADM'])
    def save() {

        def json = this.getJSONRequest()
        if(!json) return

        def nomeMapa = json.nome
        def rotas = json.rotas
        def erro = []

        if (!nomeMapa) erro.add("O nome do mapa não foi identificado!!!")
        if (nomeMapa?.length() > 30) erro.add("O nome do mapa pode conter até 30 caracteres!!!")
        if (!rotas || rotas.isEmpty()) erro.add("Deve ser fornecido ao menos uma rota válida!!!")

        if(!erro.isEmpty()) {
            render ([erro: erro] as JSON)
        }

        // persistindo novo mapa
        Mapa mapa = Mapa.get(nomeMapa) ?: new Mapa(nome: nomeMapa).save(flush: true)

        // armazena os locais antes da persistência
        def locaisOrigem = []
        def locaisDestino = []

        def rotasCadastradas = 0
        def rotasNaoCadastradas = 0
        def rotasAtualizadas = 0

        // iterando e persistindo as novas rotas
        for(rota in rotas) {

            def nomeOrigem = rota?.origem
            def nomeDestino = rota?.destino

            // validando origem e destino
            if(!nomeOrigem || nomeOrigem.length() > 30 || !nomeDestino || nomeDestino.length() > 30) {
                rotasNaoCadastradas++
                continue
            }

            Integer km

            // validando distância
            try {
                km = rota?.km

                if(km <= 0) {
                    rotasNaoCadastradas++
                    continue
                }                
            } catch(Exception e) {
                rotasNaoCadastradas++
                continue
            }

            Local origem
            Local destino

            if(locaisOrigem.contains(nomeOrigem) || locaisDestino.contains(nomeOrigem)) {
                origem = new Local(nome: nomeOrigem)
            } else {
                origem = Local.get(nomeOrigem) ?: new Local(nome: nomeOrigem).save(flush: true)
            }

            if(locaisOrigem.contains(nomeDestino) || locaisDestino.contains(nomeDestino)) {
                destino = new Local(nome: nomeDestino)
            } else {
                destino = Local.get(nomeDestino) ?: new Local(nome: nomeDestino).save(flush: true)
            }

            // armazeno as rotas do mapa nos arrays para tratar duplicidade
            locaisOrigem.add(nomeOrigem)
            locaisDestino.add(nomeDestino)

            Rota r = Rota.findByOrigemAndDestino(Local.get(nomeOrigem), Local.get(nomeDestino))
            
            // verifica se a rota já está cadastrada.
            // Caso esteja, será atualizada
            if (r) {
                if(r.km == km) {
                    rotasNaoCadastradas++
                    continue
                } else {
                    r.km = km
                    r.save(flush: true)
                    rotasAtualizadas++
                    continue
                }
                
            } else {
                new Rota(origem: Local.get(nomeOrigem), destino: Local.get(nomeDestino), km: km, mapa: mapa).save(flush: true)
            }

            rotasCadastradas++

        }

        render(
            [
                resultado: "O mapa [${mapa.nome}] foi cadastrado com sucesso.",
                rotasCadastradas: rotasCadastradas,
                rotasAtualizadas: rotasAtualizadas,
                rotasNaoCadastradas: rotasNaoCadastradas
            ] as JSON
        )

    }

    @Secured(['ROLE_USER', 'ROLE_ADM'])
    def melhorCaminho() {

        println "\n\nEncontrando o Melhor caminho..."

        def json = this.getJSONRequest()
        if(!json) return

        Local origem = Local.get(json.origem)
        Local destino = Local.get(json.destino)
        def preco, autonomia
        def erro = []

        // validando origem e destino
        if (!origem) erro.add([erroOrigem: "A origem informada não foi encontrada!!!"])
        if (!destino) erro.add([erroDestino: "O destino informado não foi encontrado!!!"])

        // validando preco e autonomia
        try {
            preco = json.preco.replaceAll(",", ".").toDouble()
        }
        catch(Exception e) {
            erro.add([erroPreco: "Valor inválido para o preço do combustível!!!"])
        }

        try {
            autonomia = json.autonomia.replaceAll(",", ".").toDouble()
        }
        catch(Exception e) {
            erro.add([erroAutonomia: "Valor inválido para a autonomia do veículo (km/l)!!!"])
        }

        if (erro.size() > 0) render (erro as JSON)

        def custo = (preco/autonomia)

        def start = new Date().getTime()
        
        def resultado = this.buscarMelhorCaminho(origem, destino, null, 0, null, custo)

        if (resultado) {
            // nivelando o mapa e extraindo a menor km
            resultado = resultado.flatten().min({it?.km})
        } else {
            erro.add([msg: "Não há rotas para o percurso escolhido!"])
            render (erro as JSON)
        }

        def stop = new Date().getTime()

        println (stop - start)

        println resultado

        render (resultado as JSON)

    }

    /**
    * Método recursivo para busca dos melhors caminhos
    * rastro -> grava a origem e verifica se o proximo destino ja foi percorrido
    */
    def buscarMelhorCaminho(origem, destino, rastro, i, kms, custo) {

        def melhorCaminho
        def rotas = Rota.findAllByOrigem(origem)
        def mapas = []

        i++

        if (rotas.size == 0) {
            return
        }

        if (!rastro) {
            rastro = []
        }
        
        rastro.add(origem)

        for (rota in rotas) {
            if (!rastro.contains(rota.destino)) {
                mapas.add(rota)
            }
        }

        rotas = mapas
        
        if(!kms) {
            kms = []
        }

        def result
        def array = []
        
        for (rota in rotas) {

            //println "${i} - ${rota.origem.nome} - ${rota.km}"
            kms.add(rota.km)

            if (rota.destino.equals(destino)) {
                def kmTotal = kms.sum()
                result = [rota:[*rastro.nome,destino.nome],km:kmTotal, custo: (kmTotal*custo).round(3)]
                array.add(result)
            } else {
                def recursivo = buscarMelhorCaminho(rota.destino, destino, rastro.clone(), i, kms.clone(), custo)
                if (recursivo) {
                    array.add(recursivo)
                }
            }

            kms.remove(kms.indexOf(rota.km))

        }

        return array
    }

    @Override
    Map getParametersToBind() {
        request.JSON
    }

}